// -------------------------------------------------
// BlinKit - BkBase Library
// -------------------------------------------------
//   File Name: pac_cache.cpp
// Description: PACCache Class
//      Author: Ziming Li
//     Created: 2022-10-03
// -------------------------------------------------
// Copyright (C) 2022 MingYang Software Technology.
// -------------------------------------------------

#include "./pac_cache.h"

#include "bkbase/http/request_impl.h"
#include "bkcommon/buffer_impl.hpp"
#include "bkcommon/response_impl.h"
#include "third_party/zed/include/zed/string/algorithm.hpp"

namespace BlinKit {

PACCache::~PACCache(void)
{
    if (nullptr == m_rt)
        return;

    if (nullptr != m_ctx)
        JS_FreeContext(m_ctx);
    JS_FreeRuntime(m_rt);
}

std::optional<PACCache::Proxy> PACCache::ChooseProxy(std::string_view proxy)
{
    auto parts = zed::split(proxy, ";");
    for (const auto &part : parts)
    {
        size_t p = part.find_first_of(zed::ascii_whitespace<char>::chars);

        std::string type(part.substr(0, p));
        zed::trim(&type);

        if (std::string::npos == p)
        {
            if (zed::strequ(type, "DIRECT"))
                return std::nullopt;
            continue;
        }

        curl_proxytype t;
        if (zed::strequ(type, "SOCKS5"))
            t = CURLPROXY_SOCKS5;
        else if (zed::strequ(type, "SOCKS"))
            t = CURLPROXY_SOCKS4;
        else if (zed::strequ(type, "PROXY"))
            t = CURLPROXY_HTTP;
        else
            continue;

        std::string server(part.substr(p + 1));
        zed::trim(&server);
        return std::make_pair(t, server);
    }
    return std::nullopt;
}

std::optional<PACCache::Proxy> PACCache::FindProxyForURL(const std::string &pacURL, const zed::url &reqURL)
{
    if (nullptr == m_rt)
        return std::nullopt;

    auto _ = m_mutex.guard();

    UpdateIfNecessary(pacURL);
    if (nullptr == m_ctx)
        return std::nullopt;

    std::optional<PACCache::Proxy> ret;
    JSValue global = JS_GetGlobalObject(m_ctx);
    JSValue func = JS_GetPropertyStr(m_ctx, global, "FindProxyForURL");
    if (JS_IsFunction(m_ctx, func))
    {
        std::string host = reqURL.get_host();

        JSValue args[2];
        args[0] = JS_NewStringLen(m_ctx, reqURL.spec().data(), reqURL.spec().length());
        args[1] = JS_NewStringLen(m_ctx, host.data(), host.length());

        JSValue proxy = JS_Call(m_ctx, func, global, 2, args);
        if (JS_IsString(proxy))
        {
            size_t len;
            const char *ps = JS_ToCStringLen(m_ctx, &len, proxy);
            ret = ChooseProxy(std::string_view(ps, len));
            JS_FreeCString(m_ctx, ps);
        }
        JS_FreeValue(m_ctx, proxy);

        JS_FreeValue(m_ctx, args[1]);
        JS_FreeValue(m_ctx, args[0]);
    }
    JS_FreeValue(m_ctx, func);
    JS_FreeValue(m_ctx, global);
    return ret;
}

void PACCache::Initialize(void)
{
    if (nullptr != m_rt)
        return;
    m_rt = JS_NewRuntime();
}

void BKAPI PACCache::OnRequestComplete(BkResponse response, void *userData)
{
    std::string code;
    response->GetData(BK_RESPONSE_BODY, BufferImpl::Wrap(code));
    reinterpret_cast<PACCache *>(userData)->Reload(code);
}

void PACCache::Reload(const std::string &code)
{
    if (nullptr != m_ctx)
        JS_FreeContext(m_ctx);
    m_ctx = JS_NewContext(m_rt);

    JSValue ret = JS_Eval(m_ctx, code.c_str(), code.length(), "pac", JS_EVAL_TYPE_GLOBAL);
    JS_FreeValue(m_ctx, ret);
}

void PACCache::UpdateIfNecessary(const std::string &pacURL)
{
    if (pacURL == m_pacURL)
        return;

    BkRequestClient client = { 0 };
    client.SizeOfStruct = sizeof(BkRequestClient);
    client.UserData = this;
    client.RequestComplete = OnRequestComplete;

    RequestImpl *req = RequestImpl::CreateInstance(pacURL.c_str(), client);
    req->SetProxy(BK_PROXY_DIRECT, nullptr);
    req->PerformSynchronously();

    m_pacURL = pacURL;
}

} // namespace BlinKit
